﻿#region Copyright(c) Anton Shelin, Vladimir Timashkov. All Rights Reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2010 Anton Shelin, Vladimir Timashkov. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1. No Trademark License - Microsoft Public License (Ms-PL) does not grant you rights to use
//      authors names, logos, or trademarks.
//   2. If you distribute any portion of the software, you must retain all copyright,
//      patent, trademark, and attribution notices that are present in the software.
//   3. If you distribute any portion of the software in source code form, you may do
//      so only under this license by including a complete copy of Microsoft Public License (Ms-PL)
//      with your distribution. If you distribute any portion of the software in compiled
//      or object code form, you may only do so under a license that complies with
//      Microsoft Public License (Ms-PL).
//   4. The names of the authors may not be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// The software is licensed "as-is." You bear the risk of using it. The authors
// give no express warranties, guarantees or conditions. You may have additional consumer
// rights under your local laws which this license cannot change. To the extent permitted
// under your local laws, the authors exclude the implied warranties of merchantability,
// fitness for a particular purpose and non-infringement.
// -----------------------------------------------------------------------------
#endregion

using System;
using System.IO;

namespace SharpDom
{
    /// <summary>Base class for HTML renders - fullor partial ones</summary>
    public abstract partial class AbstractBuilder
    {
        // Tags variables come from Tags folder: see Tags\All.cs file
        // Attributes variables come from Attributes folder: see Attributes\All.cs file

        /// <summary>Reference to parent builder (if any)</summary>
        public virtual AbstractBuilder ParentBuilder { get; set; }

        /// <summary>Contains root node to be inserted into parent tag tree</summary>
        public virtual ContainerTag TopContainer { get; set; }

        /// <summary>This method must be overriden. It creates tag hierarchy</summary>
        /// <param name="container">Container in which nested tags to be created</param>
        /// <returns>Root tag</returns>
        public abstract ContainerTag CreateTagTree(ContainerTag container);

        /// <summary>Creates builder nested into this builder</summary>
        /// <typeparam name="TBuilder">Type of nested builder</typeparam>
        /// <param name="container">Top container tag of this (parent) builder</param>
        /// <returns>Just newly created parent page builder with assigned top container</returns>
        public virtual TBuilder CreateBuilder<TBuilder>(ContainerTag container = null)
            where TBuilder : AbstractBuilder, new()
        {
            var builder = new TBuilder
            {
                ParentBuilder = this,
                TopContainer = container
            };
            builder.FireOnCreateBuilder(builder);
            return builder;
        }

        #region Maintenability

        /// <summary>Delegate of event happened against the builder</summary>
        /// <param name="sourceBuilder">Builder firing the event</param>
        /// <param name="reasonBuilder">Builder on which event initially happens</param>
        public delegate void BuilderEventDelegate(AbstractBuilder sourceBuilder, AbstractBuilder reasonBuilder);

        /// <summary>Event when the builder is created</summary>
        public event BuilderEventDelegate OnCreateBuilder;
        protected virtual void FireOnCreateBuilder(AbstractBuilder builder)
        {
            if (OnCreateBuilder != null) OnCreateBuilder(this, builder);
            if (ParentBuilder != null) ParentBuilder.FireOnCreateBuilder(builder);
        }

        /// <summary>Event when the builder is going to create tags</summary>
        public event BuilderEventDelegate OnPreCreateTags;
        protected virtual void FireOnPreCreateTags(AbstractBuilder builder = null)
        {
            if (OnPreCreateTags != null) OnPreCreateTags(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnPreCreateTags(builder ?? this);
        }

        /// <summary>Event when the builder has created tags</summary>
        public event BuilderEventDelegate OnPostCreateTags;
        protected virtual void FireOnPostCreateTags(AbstractBuilder builder = null)
        {
            if (OnPostCreateTags != null) OnPostCreateTags(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnPostCreateTags(builder ?? this);
        }

        /// <summary>Event when the builder is going to render</summary>
        public event BuilderEventDelegate OnPreRender;
        protected virtual void FireOnPreRender(AbstractBuilder builder = null)
        {
            if (OnPreRender != null) OnPreRender(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnPreRender(builder ?? this);
        }

        /// <summary>Event when the builder has rendered</summary>
        public event BuilderEventDelegate OnPostRender;
        protected virtual void FireOnPostRender(AbstractBuilder builder = null)
        {
            if (OnPostRender != null) OnPostRender(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnPostRender(builder ?? this);
        }

        /// <summary>Event when the builder is attaching placeholder contents</summary>
        public event BuilderEventDelegate OnAttachPlaceHolder;
        protected virtual void FireOnAttachPlaceHolder(AbstractBuilder builder = null)
        {
            if (OnAttachPlaceHolder != null) OnAttachPlaceHolder(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnAttachPlaceHolder(builder ?? this);
        }

        /// <summary>Event when the builder is attaching placeholder contents</summary>
        public event BuilderEventDelegate OnDeclarePlaceHolder;
        protected virtual void FireOnDeclarePlaceHolder(AbstractBuilder builder = null)
        {
            if (OnDeclarePlaceHolder != null) OnDeclarePlaceHolder(this, builder ?? this);
            if (ParentBuilder != null) ParentBuilder.FireOnDeclarePlaceHolder(builder ?? this);
        }

        #endregion
    }

    /// <summary>Delegate describing a placeholder</summary>
    /// <param name="container">Place holder tag keeping nested tags</param>
    /// <returns>Place holder tag keeping nested tags</returns>
    public delegate ContainerTag PlaceHolderDelegate(ContainerTag container);

    /// <summary>Base class for all user-defined controls</summary>
    /// <typeparam name="TViewModel">View model supplied to HTML builder in order to create tag tree</typeparam>
    public abstract partial class UserControlBuilder<TViewModel> : AbstractBuilder
    {
        // Operators come from Operators folder: see Operators folder

        /// <summary>Contains view model needed for creation of tag tree</summary>
        public virtual TViewModel Model { get; set; }

        /// <summary>Creates the hierarchy of nested tags and returns it.
        /// So outer builder can embed these tags into its own body.</summary>
        /// <param name="viewModel">View model of this builder</param>
        /// <returns>Builder tag</returns>
        public virtual ContainerTag CreateTags(TViewModel viewModel)
        {
            FireOnPreCreateTags();
            Model = viewModel;
            TopContainer = CreateTagTree(TopContainer ?? new BuilderTag<TViewModel>(this));
            FireOnPostCreateTags();
            return TopContainer;
        }

        /// <summary>Creates tags nested inside placeholder</summary>
        /// <param name="layout">Reference to virtual method generating tags inside place holder</param>
        /// <param name="defaultLayout">Reference to virtual method generating tags inside place holder</param>
        /// <returns></returns>
        public virtual ContainerTag PlaceHolder(PlaceHolderDelegate layout, Func<ContainerTag, ContainerTag> defaultLayout = null)
        {
            ContainerTag placeHolderContainer = new PlaceHolderTag(layout, defaultLayout);
            if (layout != null)
            {
                placeHolderContainer = layout(placeHolderContainer);
            }
            else if (defaultLayout != null)
            {
                placeHolderContainer = defaultLayout(placeHolderContainer);
            }
            FireOnDeclarePlaceHolder();
            return placeHolderContainer;
        }

        /// <summary>Attaches child builder's placeholder content to this builder's placeholder</summary>
        /// <typeparam name="TBuilder">The type of this instance</typeparam>
        /// <param name="method">Method assigning child builder's placeholder content to this builder's placeholder</param>
        /// <returns>This instance</returns>
        public virtual UserControlBuilder<TViewModel> Attach<TBuilder>(Action<TBuilder> method)
            where TBuilder : UserControlBuilder<TViewModel>
        {
            method((TBuilder)this);
            FireOnAttachPlaceHolder();
            return this;
        }
    }

    /// <summary>Class used for building HTML content</summary>
    /// <remarks>It includes all tags and all atributes.</remarks>
    public abstract class HtmlPageBuilder<TViewModel> : UserControlBuilder<TViewModel>
    {
        /// <summary>Enumeration of all correct document types</summary>
        public enum Doctype
        {
            Skip,
            Html401Strict,
            Html401Transitional,
            Html401Frameset,
            Xhtml10Strict,
            Xhtml10Transitional,
            Xhtml10Frameset,
            Xhtml11,
            XhtmlBasic11,
            Html5
        }

        /// <summary>HTML document type (HTML 4.01 Strinct, ect; default value - to skip on rendering)</summary>
        public virtual Doctype CurrentDoctype { get; set; }

        /// <summary>Specify whether to add indents on rendering or not</summary>
        public virtual bool IncludeIndents
        {
            get { return _includeIndents; }
            set { _includeIndents = value; }
        }
        private bool _includeIndents = true;

        /// <summary>Creates nested tags and renders them into text writer</summary>
        /// <param name="viewModel">View model required for tag creation</param>
        /// <param name="writer">Destionation of rendering</param>
        public virtual void Render(TViewModel viewModel, TextWriter writer)
        {
            FireOnPreRender();
            CreateTags(viewModel);
            RenderDocType(CurrentDoctype, writer);
            TopContainer.Render(writer, IncludeIndents);
            FireOnPostRender();
        }

        /// <summary>
        /// Renders doctype
        /// </summary>
        /// <param name="doctype">document type</param>
        /// <param name="writer">Destionation of rendering</param>
        public void RenderDocType(Doctype doctype, TextWriter writer)
        {
            var template = String.Empty;
            switch (doctype)
            {
                case Doctype.Html401Strict:
                    template = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">";
                    break;
                case Doctype.Html401Transitional:
                    template = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">";
                    break;
                case Doctype.Html401Frameset:
                    template = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" \"http://www.w3.org/TR/html4/frameset.dtd\">";
                    break;
                case Doctype.Xhtml10Strict:
                    template = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";
                    break;
                case Doctype.Xhtml10Transitional:
                    template = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
                    break;
                case Doctype.Xhtml10Frameset:
                    template = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">";
                    break;
                case Doctype.Xhtml11:
                    template = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">";
                    break;
                case Doctype.XhtmlBasic11:
                    template = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML Basic 1.1//EN\" \"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd\">";
                    break;
                case Doctype.Html5:
                    template = "<!DOCTYPE HTML>";
                    break;
                default:
                    break;
            }
            writer.WriteLine(template);
        }
    }
}